package org.rsbot.bot;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.lang.reflect.Constructor;
import java.util.EventListener;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;

import org.lazygamerz.scripting.api.Camera;
import org.lazygamerz.scripting.api.Combat;
import org.lazygamerz.scripting.api.Environment;
import org.lazygamerz.scripting.api.Equipment;
import org.lazygamerz.scripting.api.GE;
import org.lazygamerz.scripting.api.Game;
import org.lazygamerz.scripting.api.GroundItem;
import org.lazygamerz.scripting.api.Interface;
import org.lazygamerz.scripting.api.Inventory;
import org.lazygamerz.scripting.api.Keyboard;
import org.lazygamerz.scripting.api.Lobby;
import org.lazygamerz.scripting.api.Magic;
import org.lazygamerz.scripting.api.Menu;
import org.lazygamerz.scripting.api.Mouse;
import org.lazygamerz.scripting.api.NPC;
import org.lazygamerz.scripting.api.Nodes;
import org.lazygamerz.scripting.api.Objects;
import org.lazygamerz.scripting.api.Player;
import org.lazygamerz.scripting.api.Prayer;
import org.lazygamerz.scripting.api.Screen;
import org.lazygamerz.scripting.api.Settings;
import org.lazygamerz.scripting.api.Store;
import org.lazygamerz.scripting.api.Summoning;
import org.lazygamerz.scripting.api.Tile;
import org.lazygamerz.scripting.api.Walk;
import org.rsbot.Application;
import org.rsbot.client.Client;
import org.rsbot.client.input.Canvas;
import org.rsbot.event.EventManager;
import org.rsbot.event.events.PaintEvent;
import org.rsbot.event.events.TextPaintEvent;
import org.rsbot.gui.AccountManager;
import org.rsbot.script.Bank;
import org.rsbot.script.Calculations;
import org.rsbot.script.GrandExchange;
import org.rsbot.script.InputManager;
import org.rsbot.script.Methods;
import org.rsbot.script.ScriptHandler;
import org.rsbot.script.Skills;
import org.rsbot.script.antiban.BreakHandler;

public class Bot {
	private static Logger logger = Logger.getLogger(
			Bot.class.getPackage().getName());
	
    private static String account;
    private BotStub botStub;
    private static Client client;
    private Component panel;
    private PaintEvent paintEvent;
    private TextPaintEvent textPaintEvent;
    private static EventManager eventManager;
    private BufferedImage backBuffer;
    private static BufferedImage image;
    private static InputManager im;
    private RSLoader loader;
    private static ScriptHandler sh;
    private BreakHandler bh;
    private Map<String, EventListener> listeners;
    
    /**
     * Enable/disable internal client logging
     */
    public static boolean debugLogging = false;
    
    public static void debug(String s)  {
    	if (debugLogging) logger.info(s);
    }
    
    /**
     * Whether or not user input is allowed despite a script's preference.
     */
    public volatile boolean overrideInput = false;
    
    /**
     * Whether or not rendering is enabled.
     */
    public volatile boolean disableRendering = false;
    
    /**
     * Whether or not graphics are enabled.
     */
    public volatile boolean disableGraphics = false;
    
    /**
     * Whether or not all randoms are enabled.
     */
    public static boolean disableRandoms = false;
    public static boolean disableBeehiveSolver = false;
    public static boolean disableCapnArnav = false;
    public static boolean disableCerter = false;
    public static boolean disableDrillDemon = false;
    public static boolean disableExam = false;
    public static boolean disableFirstTimeDeath = false;
    public static boolean disableFreakyForester = false;
    public static boolean disableFrogCave = false;
    public static boolean disableGraveDigger = false;
    public static boolean disableLeaveSafeArea = false;
    public static boolean disableLostAndFound = false;
    public static boolean disableMaze = false;
    public static boolean disableMime = false;
    public static boolean disableMolly = false;
    public static boolean disablePillory = false;
    public static boolean disablePinball = false;
    public static boolean disablePrison = false;
    public static boolean disableQuizSolver = false;
    public static boolean disableSandwhichLady = false;
    public static boolean disableScapeRuneIsland = false;
    /**
     * Whether or not all antibans Solving is enabled.
     */
    public static boolean disableAntibans = false;
    public static boolean disableBreakHandler = true;
    public static boolean disableAutoLogin = false;
    public static boolean disableBankPins = false;
    public static boolean disableImprovedRewardsBox = false;
    public static boolean disableInterfaceCloser = false;
    public static boolean disableSystemUpdate = false;
    
    /**
     * Method singletons.
     */
    // Until such time as Methods is eliminated, it must be 
    // instantiated before any of the helper singletons.  Then,
    // later on, methods.init() must be called so Methods' 
    // helpers are initialize correctly.  We won't need to worry
    // about any of this after Methods is gone.
    public static final Methods methods = new Methods();
    
    public static Bank bank = new Bank();
    public static Calculations calculate = new Calculations();
    public static Camera camera = Camera.getInstance();
    public static Combat combat = Combat.getInstance();
    public static Environment enviro = Environment.getInstance();
    public static Equipment equipment = Equipment.getInstance();
    public static Game game = Game.getInstance();
    public static GrandExchange grandExchange = new GrandExchange();
    public static GE ge = GE.getInstance();
    public static GroundItem ground = GroundItem.getInstance();
    public static final Interface iface = Interface.getInstance();
    public static final Inventory inventory = Inventory.getInstance();
    public static final Keyboard keyboard = Keyboard.getInstance();
    public static Lobby lobby = Lobby.getInstance();
    public static Magic magic = Magic.getInstance();
    public static final Menu menu = Menu.getInstance();
    public static final Mouse mouse = Mouse.getInstance();
    public static NPC npc = NPC.getInstance();
    public static Nodes nodes = Nodes.getInstance();
    public static final Objects objects = Objects.getInstance();
    public static Player player = Player.getInstance();
    public static Prayer prayer = Prayer.getInstance();
    public static Settings settings = Settings.getInstance();
    public static Skills skills = new Skills();
    public static Store store = Store.getInstance();
    public static Screen screen = Screen.getInstance();
    public static Summoning summoning = Summoning.getInstance();
    public static Tile tile = Tile.getInstance();
    public static Walk walk = Walk.getInstance();
    
    
    /**
     * Defines what types of input are enabled when overrideInput is false.
     * Defaults to 'keyboard only' whenever a script is started.
     */
    public volatile int inputFlags = Environment.inputKeyboard | Environment.inputMouse;

    public Bot() {
        im = new InputManager();
        loader = new RSLoader();
        final Dimension size = Application.getPanelSize();
        loader.setCallback(new Runnable() {

            public void run() {
                try {
                    setClient((Client) loader.getClient());
                    resize(size.width, size.height);
                    methods.menu.setupListener();
                } catch (Exception ignored) {
                }
            }
        });
        sh = new ScriptHandler(this);
        bh = new BreakHandler();
        backBuffer = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
        image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
        paintEvent = new PaintEvent();
        textPaintEvent = new TextPaintEvent();
        eventManager = new EventManager();
        listeners = new TreeMap<String, EventListener>();

    }

    public void start() {
        try {
            loader.paint(image.getGraphics());
            loader.load();
            if (loader.getTargetName() == null) {
                return;
            }
            botStub = new BotStub(loader);
            loader.setStub(botStub);
            eventManager.start();
            botStub.setActive(true);
            ThreadGroup tg = new ThreadGroup("RSClient-" + hashCode());
            Thread thread = new Thread(tg, loader, "Loader");
            thread.start();
        } catch (Exception ignored) {
        }
    }

    public void stop() {
        eventManager.killThread(false);
        sh.stopScript();
        loader.stop();
        loader.destroy();
        loader = null;
    }

    public void resize(int width, int height) {
        backBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // client reads size of loader applet for drawing
        loader.setSize(width, height);
        // simulate loader repaint awt event dispatch
        loader.update(backBuffer.getGraphics());
        loader.paint(backBuffer.getGraphics());
    }

    public static boolean setAccount(final String name) {
        boolean exist = false;
        for (String s : AccountManager.getAccountNames()) {
            if (s.toLowerCase().equals(name.toLowerCase())) {
                exist = true;
            }
        }
        if (exist) {
            account = name;
            return true;
        }
        account = null;
        return false;
    }

    public void setPanel(Component c) {
        this.panel = c;
    }

    public void addListener(Class<?> clazz) {
        EventListener el = instantiateListener(clazz);
        listeners.put(clazz.getName(), el);
        eventManager.addListener(el);
    }

    public void removeListener(Class<?> clazz) {
        EventListener el = listeners.get(clazz.getName());
        listeners.remove(clazz.getName());
        eventManager.removeListener(el);
    }

    public boolean hasListener(Class<?> clazz) {
        return clazz != null && listeners.get(clazz.getName()) != null;
    }

    public static String getAccountName() {
        return account;
    }

    public static Client getClient() {
        return client;
    }

    public static Canvas getCanvas() {
        if (client == null) {
            return null;
        }
        return (Canvas) client.getCanvas();
    }

    public Graphics getBufferGraphics() {
        Graphics back = backBuffer.getGraphics();
        if (disableGraphics) {
            paintEvent.graphics = null;
            textPaintEvent.graphics = null;
            eventManager.processEvent(paintEvent);
            eventManager.processEvent(textPaintEvent);
            return backBuffer.getGraphics();
        }
        paintEvent.graphics = back;
        textPaintEvent.graphics = back;
        textPaintEvent.idx = 0;
        eventManager.processEvent(paintEvent);
        eventManager.processEvent(textPaintEvent);
        back.dispose();
        image.getGraphics().drawImage(backBuffer, 0, 0, null);
        if (panel != null) {
            panel.repaint();
        }
        return backBuffer.getGraphics();
    }

    public static BufferedImage getBotBuffer() {
        return getImage();
    }

    public static BufferedImage getImage() {
        return image;
    }

    public BotStub getBotStub() {
        return botStub;
    }

    public RSLoader getLoader() {
        return loader;
    }

    public Methods getMethods() {
        return methods;
    }

    public static EventManager getEventManager() {
        return eventManager;
    }

    public static InputManager getInputManager() {
        return im;
    }

    public BreakHandler getBreakHandler() {
        return bh;
    }

    public static ScriptHandler getScriptHandler() {
        return sh;
    }

    private void setClient(final Client cl) {
        client = cl;
        client.setCallback(new CallbackImpl(this));

        // In the old way, Bot's methods instance must be
        // instantiated before the helpers can be instantiated.
        // That is because the helpers all need Bot.methods to
        // be created first.
        //
        // The catch is that the Methods constructor initializes all
        // of its local helpers using Bot's members, which are not
        // yet instantiated, i.e. they are still null.
        // 
        // For this to work, methods.init() must be called after
        // Bot finishes instantiating all of the helpers.  methods.init
        // will then update method's references to the helpers using
        // Bot's created ones, thus method's helpers will no longer
        // be null.
        //
        //------------
        // In the new approach, with or without methods, Bot simply
        // creates the singletons first using getInstance for each.
        // methods and the other helpers can do the same thing.  In the
        // end, it won't matter who did it first, everyone that 
        // calls getInstance for a helper will get a working instance.
        
        // Now done as part of the static initialization of the class.
        // methods = new Methods();
        
        /* Instantiate all the helpers. */
        //bank = new Bank();
        //calculate = new Calculations();
        //camera = new Camera();
        //combat = new Combat();
        //equipment = new Equipment();
        //enviro = new Environment();
        //game = new Game();
        //grandExchange = new GrandExchange();
        //ge = new GE();
        //ground = new GroundItem();
        //iface = Interface.getInstance();
        //inventory = Inventory.getInstance();
        //keyboard = Keyboard.getInstance();
        //lobby = new Lobby();
        //magic = new Magic();
        //menu = Menu.getInstance();
        //mouse = Mouse.getInstance();
        //npc = NPC.getInstance();
        //nodes = new Nodes();
        //objects = Objects.getInstance();
        //player = new Player();
        //prayer = new Prayer();
        //settings = new Settings();
        //skills = new Skills();
        //store = new Store();
        //screen = new Screen();
        //summoning = new Summoning();
        //tile = new Tile();
        //walk = new Walk();

        // In the old way, init must now be called in order to 
        // correctly initialize method's local helper instances
        // using the now initialized Bot instances.
        //
        // i.e. methods.bank = Bot.bank;
        methods.init();


        sh.addRandoms();
        sh.addAntiban();
    }

    private EventListener instantiateListener(Class<?> clazz) {
        try {
            EventListener listener;
            try {
                final Constructor<?> constructor = clazz.getConstructor(Bot.class);
                listener = (EventListener) constructor.newInstance(this);
            } catch (Exception e) {
                listener = clazz.asSubclass(EventListener.class).newInstance();
            }
            return listener;
        } catch (Exception ignored) {
        }
        return null;
    }
}
